<?php

/**
 * @author Maxim Oransky
 * @author Alexander Makarov
 *
 * Thanks to:
 * - http://philigon.ru/
 * - http://artlebedev.ru/kovodstvo/
 * - http://pvt.livejournal.com/
 * - faZeful
 * - Naruvi
 * - Shaman
 * - Eagle
 * - grasshopper
 * - Max
 * - Reki
 * - Zav
 * ------------------------------------------------------------
 */



interface ITypoStrategy {

    /**
     * @return String
     */
    public function getSafeBlocksRegex();

    /**
     * @return Array
     */
    public function getProcessRegexRules();
}





class Typo {

    /**
     * Типографит текст.
     * @param String $str
     * @return String
     */
    public static function processText($str) { return self::getInstance()->doProcessText($str); }

    /**
     * Типографит текст, не задевая при этом тэги HTML.
     * @param String $str
     * @return String
     */
    public static function processHtml($str) { return self::getInstance()->doProcessHtml($str); }

    /**
     * @var ITypoStrategy
     */
    private $oStrategy;

    private function __construct(ITypoStrategy $oStrategy) { $this->oStrategy = $oStrategy; }

    /**
     * @return Typo
     */
    private static function getInstance() {

        static $instance = null;
        if (!$instance) $instance = new self(new TypoStrategy());

        return $instance;
    }

    private function doProcessText($str) {

        if (!trim($str)) return '';

        $str = ' ' . $str . ' ';

        foreach ($this->oStrategy->getProcessRegexRules() as $rule) {
            list($arrFrom, $arrTo) = $rule;
            $str = preg_replace($arrFrom, $arrTo, $str);
        }

        return trim($str);
    }


    private function doProcessHtml($str) {

        $oProtector = new TextBlockProtector();

        $str = $oProtector->protectText($str, $this->oStrategy->getSafeBlocksRegex());
        $str = $this->processText($str);
        $str = $oProtector->unprotectText($str);

        return $str;
    }
}

class TextBlockProtector {

    private $hStorage;

    public function protectText($text, $regex) {
        $this->hStorage = array();
        return preg_replace_callback($regex, array($this, 'add'), $text);
    }

    public function unprotectText($text) {
        return strtr($text, $this->hStorage);
    }

    private function add(array $arrMatches) {
        $key = '<tag' . count($this->hStorage) . '>';
        $this->hStorage[$key] = $arrMatches[0];
        return $key;
    }
}

class TypoStrategy implements ITypoStrategy {
    public function getProcessRegexRules() {
        return array (
  0 => 
  array (
    0 => 
    array (
      0 => '~(\\s\\d+|\\s\\d+[.,]\\d+)"([,]?[\\s]+|$)~',
      1 => '~( |\\t)+~',
      2 => '~((?:[)!?.:;#*\\\\]|$|\\w|[йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁёa-zA-Z+]|»|’|&quot;|"|\'|…|©|™|&#39;|<sup><small>®</small></sup>|\\)) +([?!:,]|…|\\.{2,5})~',
      3 => '~([йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁёa-zA-Z+]+)"([йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁёa-zA-Z+]+)"~u',
      4 => '~"([йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁёa-zA-Z+]+)"([йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁёa-zA-Z+]+)~u',
      5 => '~\\(\\s~us',
      6 => '~\\s\\)~us',
    ),
    1 => 
    array (
      0 => '$1&#34;$2',
      1 => ' ',
      2 => '$1$2',
      3 => '$1 "$2"',
      4 => '"$1" $2',
      5 => '(',
      6 => ')',
    ),
  ),
  1 => 
  array (
    0 => 
    array (
      0 => '~(?<=\\s|^|[>(])((?:<[^>]*>)*)((?:«|»|‘|’|&quot;|")+)((?:<[^>]*>)*(?:\\.{2,5}|\\w|
)(?:<[^>]*>)*)~',
      1 => '~((?:<[^>]*>)*(?:[)!?.:;#*\\\\]|$|\\w|[йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁёa-zA-Z+]|»|’|&quot;|"|\'|…|©|™|&#39;|<sup><small>®</small></sup>|\\)(?:<[^>]*>)*)((?:«|»|‘|’|&quot;|")+)((?:<[^>]*>)*(?:[)!?.:;#*\\\\]|$|\\w|[йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁёa-zA-Z+]|»|’|&quot;|"|\'|…|©|™|&#39;|<sup><small>®</small></sup>|\\)(?:<[^>]*>)*|\\s|[,<-]|$)~',
      2 => '~\\((c|с)\\)~iu',
      3 => '~\\(r\\)~i',
      4 => '~\\(tm\\)~i',
      5 => '~\\b1/2\\b~',
      6 => '~\\b1/4\\b~',
      7 => '~\\b3/4\\b~',
      8 => '~(\\d+)[x|X|х|Х|*](\\d+)~',
      9 => '~([^\\+]|^)\\+-~',
      10 => '~([^-]|^)->~',
      11 => '~<-([^-]|$)~',
      12 => '/(\\s+|^)(--?)(?=\\s)/',
      13 => '/(?<=\\d)-(?=\\d)/',
      14 => '~\\.{2,5}~',
      15 => '~\'~',
    ),
    1 => 
    array (
      0 => '$1«$3',
      1 => '$1»$3',
      2 => '©',
      3 => '<sup><small>®</small></sup>',
      4 => '™',
      5 => '&frac12;',
      6 => '&frac14;',
      7 => '&frac34;',
      8 => '$1&times;$2',
      9 => '$1&plusmn;',
      10 => '$1&rarr;',
      11 => '&larr;$1',
      12 => '$1—',
      13 => '–',
      14 => '…',
      15 => '&#39;',
    ),
  ),
  2 => 
  array (
    0 => 
    array (
      0 => '~([гГ]|гр|тов|пос)\\.\\s*([А-Я]+)~su',
      1 => '~(стр|с|табл|рис)\\.\\s*(\\d+)~sui',
      2 => '~([0-9]+)\\s*([гГ])\\.~s',
      3 => '/(\\s|^)((?:а|без|безо|в|во|вне|да|для|до|ещё|за|и|или|из|изо|их|или|к|как|ко|меж|между|на|не|ни|но|над|надо|о|об|обо|от|ото|около|при|перед|передо|по|под|подо|пред|предо|про|с|со|сквозь|там|то|у|уж|через|что|я))(\\s+)/iu',
      4 => '~((?:ЗАО|ИП|НИИ|НПФ|ОАО|ООО|ЧП))\\s+(?:«|‘|")(.*)(?:«|‘|")~',
      5 => '~(?<=\\S)(\\s+)((?:б|бы|ж|же|или|ли|либо|ль))(?=(?:<[^>]*>)*[\\s)!?.])~ui',
      6 => '~([А-ЯA-Z]\\.)\\s?([А-ЯA-Z]\\.)\\s?([А-Яа-яA-Za-z]+)~s',
      7 => '~(\\d+)\\s?(руб.)~sui',
      8 => '~(\\d+)\\s?(млн.|тыс.)?\\s?(руб.)~sui',
    ),
    1 => 
    array (
      0 => '$1.&nbsp;$2',
      1 => '$1.&nbsp;$2',
      2 => '$1&nbsp;$2.',
      3 => '$1$2&nbsp;',
      4 => '<span style="white-space:nowrap">$1 "$2"</span>',
      5 => '&nbsp;$2',
      6 => '$1$2&nbsp;$3',
      7 => '$1&nbsp;$2',
      8 => '$1&nbsp;$2&nbsp;$3',
    ),
  ),
);
    }
    public function getSafeBlocksRegex() {
        return '~((?U)(?:<pre[^>]*>.*<\\/pre>|<style[^>]*>.*<\\/style>|<script[^>]*>.*<\\/script>|<!--.*-->|<[^>]*[\\s][^>]*>))~is';
    }
}

?>